﻿#include "sptestadaptor.h"
#include "errcode.h"
#include <QVariant>
#include <QMap>
#include <QDebug>
#include <QMutex>
#include <QApplication>
#include <QWaitCondition>
#include <QMessageBox>
#include <QTextCodec>
#include <QTime>
#include <unistd.h>

#define LFS_VERSION                             0x1001
#define APP_ID                                  "SPUnitTestTool"
#define LFS_MGR_DBUS_NAME_PREF                  "org.lfs.manager"

#define DBUS_MES_CMD_COMPLETE                   "cmdComplete"
#define DBUS_MES_INFO_GOT                       "infoGot"
#define DBUS_MES_EXECUTED                       "executed"
#define DBUS_MES_EVENT_ARRIVED                  "eventArrived"

QMap<QString, int> SPTestAdaptor::g_refCounter;

SPTestAdaptor::SPTestAdaptor()
    : m_hService(NULL), m_pLFSResult(NULL), m_isWaitTimeout(false), m_isStarted(false)
    , m_actionConfirmMsgBox(NULL)
{
    QTextCodec::setCodecForLocale(QTextCodec::codecForName("GB18030"));

    connect(this, &SPTestAdaptor::doActionConfirm, this, &SPTestAdaptor::on_actionPerform, Qt::BlockingQueuedConnection);
    connect(this, &SPTestAdaptor::sendMessage, this, &SPTestAdaptor::on_message, Qt::QueuedConnection);

    m_mutex = new QMutex();
    m_waitCondition = new QWaitCondition();

    memset(m_szDBusObjectName, 0, sizeof(m_szDBusObjectName));

    LFSVERSION v;
    HRESULT hResult = LFSStartUp(LFS_VERSION, &v);
    Q_ASSERT(hResult == LFS_SUCCESS ||  hResult == LFS_ERR_ALREADY_STARTED);
}

SPTestAdaptor::~SPTestAdaptor()
{
    requestInterruption();
    wait();

    delete m_waitCondition;
    delete m_mutex;
}

int SPTestAdaptor::Open(QVariant &logicalName, QVariant& timeout, QVariant& LFSResult)
{
    if (!logicalName.canConvert<QString>())
        return ERR_PARAM;

    if (timeout.toString().isEmpty() || !timeout.canConvert<int>())
        timeout.setValue((ulong)ULONG_MAX);

    REQUESTID requestId = 0;
    LFSVERSION svc_v, spi_v;
    DWORD trace_level = LFS_TRACE_API | LFS_TRACE_ALL_API | LFS_TRACE_SPI | LFS_TRACE_ALL_SPI | LFS_TRACE_MGR;
    HRESULT hResult = LFSAsyncOpen(logicalName.toString().toUtf8().data(), LFS_DEFAULT_HAPP, APP_ID, trace_level,
        LFS_INDEFINITE_WAIT, &m_hService, m_szDBusObjectName, LFS_VERSION, &svc_v, &spi_v, &requestId);
    if (LFS_SUCCESS != hResult)
        return ERR_INVOKE;

    LPLFSRESULT pLFSResult = NULL;
    if (!waitForResult(&pLFSResult, timeout.toInt()))
        return ERR_TIMEOUT;

    hResult = pLFSResult->result;
    LFSResult.setValue((long)hResult);
    FreeResult(pLFSResult);
    return hResult == LFS_SUCCESS ? ERR_OK : ERR_OTHER;
}

int SPTestAdaptor::Register(QVariant& timeout, QVariant& LFSResult)
{
    if (timeout.toString().isEmpty() || !timeout.canConvert<int>())
        timeout.setValue((ulong)ULONG_MAX);

    REQUESTID requestId = 0;
    HRESULT hResult = LFSAsyncRegister(m_hService, EXECUTE_EVENTS | SERVICE_EVENTS | USER_EVENTS | SYSTEM_EVENTS,
                                       m_szDBusObjectName, m_szDBusObjectName, &requestId);
    if (LFS_SUCCESS != hResult)
        return ERR_INVOKE;

    LPLFSRESULT pLFSResult = NULL;
    if (!waitForResult(&pLFSResult, timeout.toInt()))
        return ERR_TIMEOUT;

    hResult = pLFSResult->result;
    LFSResult.setValue((long)hResult);
    FreeResult(pLFSResult);
    return hResult == LFS_SUCCESS ? ERR_OK : ERR_OTHER;
}

int SPTestAdaptor::Deregister(QVariant &timeout, QVariant &LFSResult)
{
    if (timeout.toString().isEmpty() || !timeout.canConvert<int>())
        timeout.setValue((ulong)ULONG_MAX);

    REQUESTID requestId = 0;
    HRESULT hResult = LFSAsyncDeregister(m_hService, EXECUTE_EVENTS | SERVICE_EVENTS | USER_EVENTS | SYSTEM_EVENTS,
                                         m_szDBusObjectName, m_szDBusObjectName, &requestId);
    if (LFS_SUCCESS != hResult)
        return ERR_INVOKE;

    LPLFSRESULT pLFSResult = NULL;
    if (!waitForResult(&pLFSResult, timeout.toInt()))
        return ERR_TIMEOUT;

    hResult = pLFSResult->result;
    LFSResult.setValue((long)hResult);
    FreeResult(pLFSResult);
    return hResult == LFS_SUCCESS ? ERR_OK : ERR_OTHER;
}

int SPTestAdaptor::Close(QVariant &timeout, QVariant &LFSResult)
{
    if (timeout.toString().isEmpty() || !timeout.canConvert<int>())
        timeout.setValue((ulong)ULONG_MAX);

    REQUESTID requestId = 0;
    HRESULT hResult = LFSAsyncClose(m_hService, m_szDBusObjectName, &requestId);
    if (LFS_SUCCESS != hResult)
        return ERR_INVOKE;

    LPLFSRESULT pLFSResult = NULL;
    if (!waitForResult(&pLFSResult, timeout.toInt()))
        return ERR_TIMEOUT;

    m_hService = NULL;

    hResult = pLFSResult->result;
    LFSResult.setValue((long)hResult);
    FreeResult(pLFSResult);
    return hResult == LFS_SUCCESS ? ERR_OK : ERR_OTHER;
}

int SPTestAdaptor::ActionConfirm(QVariant &timeout, QVariant &title, QVariant &text, QVariant& detailedText)
{
    if (timeout.toString().isEmpty() || !timeout.canConvert<int>())
        timeout.setValue((int)INT_MAX);

    if (!title.canConvert<QString>() || title.toString().isEmpty())
        title.setValue(QStringLiteral("动作确认"));

    if (!text.canConvert<QString>() || text.toString().isEmpty())
        text.setValue(QStringLiteral("请确认以上动作都已正确执行了吗？"));

    if (!detailedText.canConvert<QString>() || detailedText.toString().isEmpty())
        detailedText.setValue(QStringLiteral(""));

    emit doActionConfirm(title.toString(), text.toString(), detailedText.toString());
    QTime timeUsed;
    timeUsed.start();
    while (m_actionConfirmMsgBox->isVisible() && timeUsed.elapsed() < timeout.toInt())
        QThread::msleep(1);

    if (timeUsed.elapsed() >= timeout.toInt())
        return ERR_TIMEOUT;
    else
        return m_actionConfirmMsgBox->clickedButton() == m_actionConfirmMsgBox->button(QMessageBox::Yes) ? ERR_OK : ERR_INVOKE;

    delete m_actionConfirmMsgBox;
    m_actionConfirmMsgBox = NULL;
}

static void reply_to_introspect_call(DBusMessage* msg, DBusConnection* conn)
{
    DBusMessage* reply;
    DBusMessageIter args;
    dbus_uint32_t serial = 0;

    // create a reply from the message
    reply = dbus_message_new_method_return(msg);

    // add the arguments to the reply
    char* info = "No reply...";
    dbus_message_iter_init_append(reply, &args);
    if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &info)) {
        qCritical("Out Of Memory!");
        qApp->exit(-1);
        return;
    }
    // send the reply && flush the connection
    if (!dbus_connection_send(conn, reply, &serial)) {
        qCritical("Out Of Memory!");
        qApp->exit(-1);
        return;
    }
    dbus_connection_flush(conn);

    // free the reply
    dbus_message_unref(reply);
}

void SPTestAdaptor::run()
{
    DBusError err;
    DBusConnection* conn;
    int ret;
    // initialise the errors
    dbus_error_init(&err);

    // connect to the bus
    conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
    if (dbus_error_is_set(&err)) {
        qCritical("Connection Error (%s)", err.message);
        dbus_error_free(&err);
    }
    if (NULL == conn) {
        qApp->exit(-1);
        return;
    }

    // request a name on the bus
    stringstream dbus_name;
    dbus_name << LFS_MGR_DBUS_NAME_PREF << ".p" << getpid();
    qDebug() << "dbus name: " << dbus_name.str().c_str();
    ret = dbus_bus_request_name(conn, dbus_name.str().c_str(),
                                DBUS_NAME_FLAG_REPLACE_EXISTING
                                , &err);
    if (dbus_error_is_set(&err)) {
        qCritical("Name Error (%s)", err.message);
        dbus_error_free(&err);
    }
    if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
        qApp->exit(-1);
        return;
    }

    // add a rule for which messages we want to see
    dbus_bus_add_match(conn,
                       "type='signal',interface='org.lfs.windows_compatible_intf'",
                       &err); // see signals from the given interface
    dbus_connection_flush(conn);
    if (dbus_error_is_set(&err)) {
        qCritical("Match Error (%s)", err.message);
        dbus_error_free(&err);
        qApp->exit(-1);
        return;
    }

    m_isStarted = true;

    while (!isInterruptionRequested()) {
        // non blocking read of the next available message
        dbus_connection_read_write(conn, 0);
        DBusMessage* msg = dbus_connection_pop_message(conn);

        // loop again if we haven't read a message
        if (NULL == msg)
            goto LOOP_END;

        // check if the message is a signal from the correct interface and with the correct name
        if (DBUS_MESSAGE_TYPE_SIGNAL == dbus_message_get_type(msg)) {
            const char* interface = dbus_message_get_interface(msg);
            if (strcmp(interface, "org.lfs.windows_compatible_intf") != 0)
                goto LOOP_END;

            const char* path = dbus_message_get_path(msg);
            if (strcmp(path, m_szDBusObjectName) != 0)
                goto LOOP_END;

            qint64 param1, param2;
            if (!parseParam(msg, &param1, &param2))
                goto LOOP_END;
            emit sendMessage(dbus_message_get_member(msg), param1, param2);
        }
        else if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect"))
            reply_to_introspect_call(msg, conn);
        else if (dbus_message_is_method_call(msg, "org.lfs.windows_compatible_intf", LFS_MGR_DBUS_METHOD_NAME)) {
            const char* path = dbus_message_get_path(msg);
            if (strcmp(path, m_szDBusObjectName) != 0)
                goto LOOP_END;

            qint64 param1, param2;
            if (!parseParam(msg, &param1, &param2))
                goto LOOP_END;
            eventCallBack(param1, param2);
        }

        // free the message
        dbus_message_unref(msg);

LOOP_END:
        sleep(1);
    }

    dbus_bus_release_name(conn, dbus_name.str().c_str(), &err);
    dbus_connection_unref(conn);
}

bool SPTestAdaptor::waitForResult(LPLFSRESULT* ppResult, unsigned long time)
{
    m_mutex->lock();

    bool ret = m_waitCondition->wait(m_mutex, time);
    if (ret) {
        *ppResult = m_pLFSResult;
        m_isWaitTimeout = false;
    }
    else {
        *ppResult = NULL;
        m_isWaitTimeout = true;
    }

    m_mutex->unlock();
    return ret;
}

void SPTestAdaptor::FreeResult(LPLFSRESULT pResult)
{
    LFSFreeResult(pResult);
}

void SPTestAdaptor::on_message(const char *msg, qint64 param1, qint64 param2)
{
    if (strcmp(msg, DBUS_MES_CMD_COMPLETE) == 0)             // 命令完成
        on_cmdComplete(param1, param2);
    else if (strcmp(msg, DBUS_MES_INFO_GOT) == 0)            // 查询信息返回
        on_infoGot(param1);
    else if (strcmp(msg, DBUS_MES_EXECUTED) == NULL)         // 执行指令返回
        on_executed(param1);
    else if (strcmp(msg, DBUS_MES_EVENT_ARRIVED) == NULL)    // 事件返回
        on_eventArrived(param1, param2);
    else
        qWarning("Error message: %s", msg);
}

void SPTestAdaptor::on_actionPerform(const QString &title, const QString &text, const QString& detailedText)
{
    m_actionConfirmMsgBox = new QMessageBox(QMessageBox::Question, title, text,
                                            QMessageBox::Yes | QMessageBox::No);
    m_actionConfirmMsgBox->setDetailedText(detailedText);
    m_actionConfirmMsgBox->setWindowModality(Qt::ApplicationModal);
    m_actionConfirmMsgBox->show();
}

void SPTestAdaptor::generateDBusObjectName()
{
    int refCount = ++g_refCounter[this->metaObject()->className()];
    sprintf(m_szDBusObjectName, "/lfs/%s%03d/listener",
            this->metaObject()->className(),
            refCount);
}

void SPTestAdaptor::releaseDBusObjectName()
{
    QString className = this->metaObject()->className();
    if (--g_refCounter[className] == 0)
        g_refCounter.remove(className);
}

void SPTestAdaptor::startDBusListener()
{
    m_isStarted = false;
    this->start();
    while (!m_isStarted)
        QThread::msleep(100);
}

void SPTestAdaptor::on_cmdComplete(qint64 msg_id, qint64 pointer)
{
    m_mutex->lock();
    qDebug() << "msg_id: " << msg_id;
    m_pLFSResult = reinterpret_cast<LPLFSRESULT>(pointer);
    if (m_isWaitTimeout)
        FreeResult(m_pLFSResult);
    m_waitCondition->wakeAll();
    m_mutex->unlock();
}

void SPTestAdaptor::on_infoGot(qint64 pointer)
{
    m_mutex->lock();
    m_pLFSResult = reinterpret_cast<LPLFSRESULT>(pointer);
    if (m_isWaitTimeout)
        FreeResult(m_pLFSResult);
    m_waitCondition->wakeAll();
    m_mutex->unlock();
}

void SPTestAdaptor::on_executed(qint64 pointer)
{
    m_mutex->lock();
    m_pLFSResult = reinterpret_cast<LPLFSRESULT>(pointer);
    if (m_isWaitTimeout)
        FreeResult(m_pLFSResult);
    m_waitCondition->wakeAll();
    m_mutex->unlock();
}

void SPTestAdaptor::on_eventArrived(qint64 event_id, qint64 pointer)
{
    LPLFSRESULT lpResult = LPLFSRESULT(pointer);
    qDebug() << "event_id: " << event_id;
    FreeResult(lpResult);
}

void SPTestAdaptor::eventCallBack(int64_t msg_id, int64_t pointer)
{
    switch (msg_id)
    {
        case LFS_OPEN_COMPLETE:
        case LFS_CLOSE_COMPLETE:
        case LFS_LOCK_COMPLETE:
        case LFS_UNLOCK_COMPLETE:
        case LFS_REGISTER_COMPLETE:
        case LFS_DEREGISTER_COMPLETE:
            on_cmdComplete(msg_id, pointer);
            break;
        case LFS_GETINFO_COMPLETE:
            on_infoGot(pointer);
            break;
        case LFS_EXECUTE_COMPLETE:
            on_executed(pointer);
            break;
        case LFS_EXECUTE_EVENT:
        case LFS_SERVICE_EVENT:
        case LFS_USER_EVENT:
        case LFS_SYSTEM_EVENT:
            on_eventArrived(msg_id, pointer);
            break;
    }
}

bool SPTestAdaptor::parseParam(DBusMessage *msg, qint64* param1, qint64* param2)
{
    const char* message = dbus_message_get_member(msg);
    DBusMessageIter args;
    // read the parameters
    if (!dbus_message_iter_init(msg, &args)) {
        qWarning("Message (%s) has no arguments!", message);
        return false;
    }

    int i = 0;
    dbus_int64_t arg_vals[2] = {0};
    do {
        if (i >= 2) {
            qWarning("Message (%s) has too many arguments!", message);
            return false;
        }

        int arg_type = dbus_message_iter_get_arg_type(&args);
        if (DBUS_TYPE_INT64 != arg_type) {
            qWarning("Message (%s) has an argument is not the type of int64!", message);
            return false;
        }

        dbus_message_iter_get_basic(&args, &arg_vals[i++]);
    } while (dbus_message_iter_next(&args));

    *param1 = arg_vals[0];
    *param2 = arg_vals[1];
    return true;
}

void TrimSpace(char* str)
{
    char *start = str - 1;
    char *end = str;
    char *p = str;
    while(*p)
    {
        switch(*p)
        {
        case ' ':
        case '\r':
        case '\n':
            {
                if(start + 1 == p)
                    start = p;
            }
            break;
        default:
            break;
        }
        ++p;
    }
    //现在来到了字符串的尾部 反向向前
    --p;
    ++start;
    if(*start == 0)
    {
        //已经到字符串的末尾了
        *str = 0 ;
        return;
    }
    end = p + 1;
    while(p > start)
    {
        switch(*p)
        {
        case ' ':
        case '\r':
        case '\n':
            {
                if(end - 1 == p)
                    end = p;
            }
            break;
        default:
            break;
        }
        --p;
    }

    memmove(str, start, end - start);
    int offset = end - start;
    *(str + offset) = '\0';
}

void ParseExtraInfo(stringstream& ss, LPSTR lpszExtra)
{
    int i = 0;
    string s;
    int status = 0;
    bool exitLoop = false;

    while (1) {
        switch (status) {
            case 0:
                if (lpszExtra[i] != '\0') {
                    char szInfo[128] = {0};
                    strncpy(szInfo, &lpszExtra[i], 128);
                    TrimSpace(szInfo);
                    s.append(szInfo);
                    i += strlen(&lpszExtra[i]);
                }
                else
                    status = 1;
                break;
            case 1:
                ss << s << ";";
                s.clear();
                i++;
                if (lpszExtra[i] != '\0')
                    status = 0;
                else
                    status = 2;
                break;
            case 2:
                exitLoop = true;
                break;
        }

        if (exitLoop)
            break;
    }
}
